package zdream.pmfield.stage.director;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

import zdream.pmfield.data.foe.BStat;
import zdream.pmfield.stage.buff.foe.FoeStageBuff;
import zdream.pmfield.stage.buff.seat.SeatStageBuff;
import zdream.pmfield.stage.buff.StageBuff;
import zdream.pmfield.stage.object.FoeActor;

import static zdream.pmfield.stage.director.HookType.*;

/**
 * @author Zdream
 * @date 2023-11-23
 * @since 0.0.1
 */
public class Hooks {
	private static final Map<HookType, Supplier<HookContext>> FACTORIES = new HashMap<>();

	static {
		FACTORIES.put(BEFORE_FOE_RECEIVE_BUFF, BeforeFoeReceiveBuffContext::new);
		FACTORIES.put(BEFORE_SEAT_RECEIVE_BUFF, BeforeSeatReceiveBuffContext::new);

		FACTORIES.put(BEFORE_FOE_TURN_START, () -> new BeforeTurnHookContext(BEFORE_FOE_TURN_START));
		FACTORIES.put(BEFORE_FOE_ACTION_REQUEST, () -> new HookContext(BEFORE_FOE_ACTION_REQUEST));

		FACTORIES.put(DURING_ROLL_CHECK, () -> new RollCheckHookContext(DURING_ROLL_CHECK));

		FACTORIES.put(ON_FOE_REFRESH_VSTAT, () -> new FoeBuffContext(ON_FOE_REFRESH_VSTAT));
		FACTORIES.put(ON_FOE_REFRESH_BSTAT, RefreshFoeBStatContext::new);
		FACTORIES.put(ON_FOE_UPDATE_BSTAT_LEVEL, FoeBStatLevelUpdateContext::new);

		FACTORIES.put(BEFORE_ATTACK_ROLL_CHECK, () -> new AttackHookContext(BEFORE_ATTACK_ROLL_CHECK));
		FACTORIES.put(AFTER_EXECUTE_ATTACK, () -> new AttackHookContext(AFTER_EXECUTE_ATTACK));
		FACTORIES.put(ON_CHECK_TYPE_FIXED, () -> new AttackHookContext(ON_CHECK_TYPE_FIXED));
		FACTORIES.put(AFTER_EXECUTE_DEFENSE, () -> new AttackHookContext(AFTER_EXECUTE_DEFENSE));

		FACTORIES.put(ON_CREATE_BUFF_BY_ABILITY, () -> new FoeBuffContext(ON_CREATE_BUFF_BY_ABILITY));

		FACTORIES.put(BEFORE_FOE_LEAVE_STAGE, () -> new FoeBuffContext(BEFORE_FOE_LEAVE_STAGE));
	}

	public static HookContext triggerHook(HookType hookType, HookStore store) {
		final HookContext context = FACTORIES.get(hookType).get();
		if (context == null) {
			throw new NullPointerException("你看看你是不是又忘记在 HookType 下创建 FACTORIES 了? hookType = " + hookType);
		}
		store.fill(context);
		return context;
	}

	public static class HookContext {
		public final HookType hookType;
		public final List<HookStore.HookHandler> handlers = new ArrayList<>();
		public boolean aborted = false;
		public HookStore.HookHandler abortBy;

		public HookContext(HookType hookType) {
			this.hookType = Objects.requireNonNull(hookType);
		}

		@SuppressWarnings("unchecked")
		public <C extends HookContext> C as() {
			return (C) this;
		}

		public void run() {
			if (handlers.isEmpty()) {
				return;
			}

			handlers.sort(null);
			for (HookStore.HookHandler handler : handlers) {
				handler.handler.accept(this);
				if (this.aborted) {
					abortBy = handler;
					break;
				}
			}
		}

		public void abort() {
			this.aborted = true;
		}

		public <T extends IHookStorage> HookContext appendAll(Collection<T> collection) {
			for (IHookStorage storage : collection) {
				storage.getHookStore().fill(this);
			}
			return this;
		}

		public HookContext append(IHookStorage storage) {
			storage.getHookStore().fill(this);
			return this;
		}
	}

	public static class FoeBuffContext extends HookContext {
		public FoeActor foe;

		public FoeBuffContext(HookType hookType) {
			super(hookType);
		}

		public FoeBuffContext withFoe(FoeActor foe) {
			this.foe = foe;
			return this;
		}
	}

	public static class RefreshFoeBStatContext extends HookContext {
		public FoeActor foe;
		public final float[] fixed = new float[BStat.values().length];

		public RefreshFoeBStatContext() {
			super(ON_FOE_REFRESH_BSTAT);
			Arrays.fill(fixed, 1.0f);
		}

		public RefreshFoeBStatContext withFoe(FoeActor foe) {
			this.foe = foe;
			return this;
		}
	}

	public static class FoeBStatLevelUpdateContext extends HookContext {
		public FoeActor.BStatLevelUpdateReport report;

		public FoeBStatLevelUpdateContext() {
			super(ON_FOE_UPDATE_BSTAT_LEVEL);
		}

		public FoeBStatLevelUpdateContext withReport(FoeActor.BStatLevelUpdateReport report) {
			this.report = report;
			return this;
		}
	}

	public static class BeforeFoeReceiveBuffContext extends HookContext {
		public FoeStageBuff otherBuff;

		public BeforeFoeReceiveBuffContext() {
			super(BEFORE_FOE_RECEIVE_BUFF);
		}

		public BeforeFoeReceiveBuffContext withOtherBuff(FoeStageBuff otherBuff) {
			this.otherBuff = otherBuff;
			return this;
		}
	}

	public static class BeforeSeatReceiveBuffContext extends HookContext {
		public SeatStageBuff otherBuff;

		public BeforeSeatReceiveBuffContext() {
			super(BEFORE_SEAT_RECEIVE_BUFF);
		}

		public BeforeSeatReceiveBuffContext withOtherBuff(SeatStageBuff otherBuff) {
			this.otherBuff = otherBuff;
			return this;
		}
	}

	public static class BeforeTurnHookContext extends HookContext {
		public FoeActor foe;
		public int movePointRollCount = 2;
		public boolean allowAct = true, allowMove = true;
		public StageBuff<?> allowActRejectBy, allowMoveRejectBy;

		public BeforeTurnHookContext(HookType type) {
			super(type);
		}

		public BeforeTurnHookContext withFoe(FoeActor foe) {
			this.foe = foe;
			return this;
		}

		public BeforeTurnHookContext withMovePointRollCount(int movePointRollCount) {
			this.movePointRollCount = movePointRollCount;
			return this;
		}
	}

	public static class RollCheckHookContext extends HookContext {
		public String title;
		public Roll[] rolls;
		public FoeActor foe;

		public RollCheckHookContext(HookType type) {
			super(type);
		}

		public RollCheckHookContext withRolls(Roll[] rolls) {
			this.rolls = rolls;
			return this;
		}

		public RollCheckHookContext withTitle(String title) {
			this.title = title;
			return this;
		}

		public RollCheckHookContext withFoe(FoeActor foe) {
			this.foe = foe;
			return this;
		}
	}

	public static class AttackHookContext extends HookContext {
		public AttackContext attackContext;

		public AttackHookContext(HookType type) {
			super(type);
		}

		public AttackHookContext withAttackContext(AttackContext attackContext) {
			this.attackContext = attackContext;
			return this;
		}
	}
}
